﻿#region

using System;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Security.Principal;
using System.Threading;
using System.Web;

#endregion

namespace AjaxControlToolkit.LongOperations
{
    internal class ThreadInfo
    {
        // Cached type information.
        //
        private const BindingFlags _bfNonPublicInstance = BindingFlags.Instance | BindingFlags.NonPublic;
        private const BindingFlags _bfNonPublicStatic = BindingFlags.Static | BindingFlags.NonPublic;

        private static readonly MethodInfo MiGetLogicalCallContext =
            typeof (Thread).GetMethod("GetLogicalCallContext", _bfNonPublicInstance);

        private static readonly MethodInfo MiSetLogicalCallContext =
            typeof (Thread).GetMethod("SetLogicalCallContext", _bfNonPublicInstance);

        private static readonly string HttpContextSlotName;
        private readonly LogicalCallContext _callContext;
        private readonly HttpContext _httpContext;
        private readonly IPrincipal _principal;
        private CompressedStack compressedStack; // Always null until Get/SetCompressedStack are opened up.

        static ThreadInfo()
        {
            // Lookup the value of HttpContext.CallContextSlotName (if it exists)
            // to see what the name of the call context slot is where HttpContext.Current
            // is stashed.  As a fallback, if this field isn't present anymore, just
            // try for the original "HttpContext" slot name.
            //
            FieldInfo fi = typeof (HttpContext).GetField("CallContextSlotName", _bfNonPublicStatic);

            if (fi != null)
            {
                HttpContextSlotName = (string) fi.GetValue(null);
            }
            else
            {
                HttpContextSlotName = "HttpContext";
            }
        }

        private ThreadInfo(bool propogateThreadPrincipal, bool propogateCallContext,
                           bool propogateHttpContext, bool propogateCASMarkers)
        {
            if (propogateThreadPrincipal)
            {
                _principal = Thread.CurrentPrincipal;
            }

            if (propogateHttpContext)
            {
                _httpContext = HttpContext.Current;
            }

            if (propogateCallContext && (MiGetLogicalCallContext != null))
            {
                _callContext = (LogicalCallContext) MiGetLogicalCallContext.Invoke(Thread.CurrentThread, null);
                _callContext = (LogicalCallContext) _callContext.Clone();

                // TODO: consider serialize/deserialize call context to get a MBV snapshot
                //       instead of leaving it up to the Clone method.
            }

            if (propogateCASMarkers)
            {
                // TODO: Uncomment the following when Thread.GetCompressedStack is no longer guarded
                //       by a StrongNameIdentityPermission.
                //
                // compressedStack = Thread.CurrentThread.GetCompressedStack();
            }
        }

        public static ThreadInfo Capture(bool propogateThreadPrincipal, bool propogateCallContext,
                                         bool propogateHttpContext, bool propogateCASMarkers)
        {
            return new ThreadInfo(propogateThreadPrincipal, propogateCallContext, propogateHttpContext, propogateCASMarkers);
        }

        public static ThreadInfo Impersonate(ThreadInfo ti)
        {
            if (ti == null)
            {
                throw new ArgumentNullException("ti");
            }

            ThreadInfo prevInfo = Capture(true, true, true, true);
            Restore(ti);
            return (prevInfo);
        }

        public static void Restore(ThreadInfo ti)
        {
            if (ti == null)
            {
                throw new ArgumentNullException("ti");
            }

            // Restore call context.
            //
            if (MiSetLogicalCallContext != null)
            {
                MiSetLogicalCallContext.Invoke(Thread.CurrentThread, new object[] {ti._callContext});
            }

            // Restore HttpContext with the moral equivalent of
            // HttpContext.Current = ti.httpContext;
            //
            CallContext.SetData(HttpContextSlotName, ti._httpContext);

            // Restore thread identity.  It's important that this be done after
            // restoring call context above, since restoring call context also
            // overwrites the current thread principal setting.  If propogateCallContext
            // and propogateThreadPrincipal are both true, then the following is redundant.
            // However, since propogating call context requires the use of reflection
            // to capture/restore call context, I want that behavior to be independantly
            // switchable so that it can be disabled; while still allowing thread principal
            // to be propogated.  This also covers us in the event that call context
            // propogation changes so that it no longer propogates thread principal.
            //
            Thread.CurrentPrincipal = ti._principal;

            if (ti.compressedStack != null)
            {
                // TODO: Uncomment the following when Thread.SetCompressedStack is no longer guarded
                //       by a StrongNameIdentityPermission.
                //
                // Thread.CurrentThread.SetCompressedStack(ti.compressedStack);
            }
        }
    }
}